package provisioner.security;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import javax.security.auth.x500.X500Principal;


import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.KeyGenerationParameters;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.util.encoders.Hex;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

public class CertificateGenerator {

   private String CAKeyFile;
   private String CACertFile;

   public CertificateGenerator(String CAKeyFile, String CACertFile) throws Exception {
      this.CAKeyFile = CAKeyFile;
      this.CACertFile = CACertFile;
   }

   public X509Certificate generateCertificate(String instanceId, KeyPair keyPair) throws Exception {
      Calendar t = Calendar.getInstance();
      t.add(Calendar.DATE,-1);
      Date startDate = t.getTime();                // time from which certificate is valid
      t.add(Calendar.DATE,365);
      Date expiryDate = t.getTime();  // time after which certificate is not valid
      BigInteger serialNumber = new BigInteger("999999");   // serial number for certificate
      String CAPassword = "";
      KeyPair CAKeyPair = readKeyPair(this.CAKeyFile,CAPassword.toCharArray());
      PrivateKey caKey = CAKeyPair.getPrivate();   // private key of the certifying authority (ca) certificate
      X509Certificate caCert = readCertificate(this.CACertFile);  // public key certificate of the certifying authority

      X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
      X500Principal subjectName = new X500Principal("CN=" + instanceId);

      certGen.setSerialNumber(serialNumber);
      certGen.setIssuerDN(caCert.getSubjectX500Principal());
      certGen.setNotBefore(startDate);
      certGen.setNotAfter(expiryDate);
      certGen.setSubjectDN(subjectName);
      certGen.setPublicKey(keyPair.getPublic());
      certGen.setSignatureAlgorithm("SHA1WithRSAEncryption");

      certGen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                        new AuthorityKeyIdentifierStructure(caCert));
      certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
                        new SubjectKeyIdentifierStructure(keyPair.getPublic()));

      X509Certificate cert = certGen.generate(caKey, "BC");   // note: private key of CA
      return cert;
   }

   private KeyPair readKeyPair(String privateKeyFile, char [] keyPassword) throws Exception {
      FileReader fileReader = new FileReader(new File(privateKeyFile));
      PEMReader r = new PEMReader(fileReader, new DefaultPasswordFinder(keyPassword));
      try {
         return (KeyPair) r.readObject();
      } catch (IOException ex) {
         throw new IOException("The private key could not be decrypted", ex);
      } finally {
         r.close();
         fileReader.close();
      }
   }

   private class DefaultPasswordFinder implements PasswordFinder {

      private final char [] password;

      private DefaultPasswordFinder(char [] password) {
         this.password = password;
      }

      @Override
      public char[] getPassword() {
         return Arrays.copyOf(password, password.length);
      }
   } 

   private X509Certificate readCertificate(String certFile) throws Exception {
      InputStream inStream = new FileInputStream(certFile);
      CertificateFactory cf = CertificateFactory.getInstance("X.509");
      X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
      inStream.close();
      return cert;
   }

   private AsymmetricCipherKeyPair generateKeyPair(int keySizeInBits) {
      RSAKeyPairGenerator r = new RSAKeyPairGenerator();
      r.init(new KeyGenerationParameters(new SecureRandom(),keySizeInBits));
      AsymmetricCipherKeyPair keys = r.generateKeyPair();
      return keys;
   }

   public static String convertToPEM(Object o) throws Exception {
      StringWriter sw = new StringWriter();
      PEMWriter writer = new PEMWriter(sw,"BC");
      writer.writeObject(o);
      writer.close();
      sw.close();
      return sw.toString();
   }

   public static void main(String[] args) throws Exception {
      Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
      KeyPair keyPair = PublicPrivateKeysGenerator.generateKeys("RSA",1024);
      CertificateGenerator certGen = new CertificateGenerator("/home/ekasit/src/typica-1.7/ca.key","/home/ekasit/src/typica-1.7/ca.crt");
      X509Certificate cert = certGen.generateCertificate("New Instance",keyPair);
      StringWriter sw = new StringWriter();
      PEMWriter writer = new PEMWriter(sw,"BC");
      writer.writeObject(cert);
      writer.close();
      sw.close();
      System.out.println(sw);
      System.out.println(PublicPrivateKeysGenerator.convertToPEM(keyPair.getPrivate()));
 
   }

}
